/*
 * power.c
 *
 * power management driver.
 *
 * Copyright (C) 2022 Microwell, Inc.
 * Subject to the GNU Public License, version 2.
 *
 * Author: guoming<guoming@microwell.net>
 */
#include "power_supply.h"
#include "core.h"

/* wakeup reason from STOP mode */
#define POWER_DCINWKF           (1 << 0)
#define POWER_VKEYWKF           (1 << 1)
#define POWER_P03WKF            (1 << 2)
#define POWER_P04WKF            (1 << 3)
#define POWER_P06WKF            (1 << 4)
#define POWER_P07WKF            (1 << 5)
#define POWER_P11WKF            (1 << 6)
#define POWER_WDOGWKF           (1 << 7)

/*
 * MFP status backup for P03/P04/P06/P07/P11,
 * before STOP, we should ensure all the I/Os' MFP are
 * switched to MFP input, or else, STOP or WAKEUP will
 * be abnormal
 */

static uint8_t get_wakeup_reason(void);

void power_init(void)
{
    CLEAR_BIT(PWRCON1, CLEAR_OVD);
#if (CONFIG_POWER_USING_OVD)
    /* OVD thresh: 0 5.6/5.4V, 1 6/5.5V */
    SET_BIT(PWRCON1, (OVDSET | OVDEN));
#endif

    CLEAR_BIT(PWRCON2, CLR_LV_STU);
    SET_BIT(PWRCON2, LV_STU);

    CLEAR_BIT(PWRCON4, OTDEN);
    SET_BIT(PWRCON4, OTDEN);

#if (CONFIG_POWER_USING_DCIN_WK > 0)
    /* DCIN insertion wake-up mode, and enable  */
    MODIFY_REG(CHGCON4, DCINWKM, (((CONFIG_POWER_USING_DCIN_WK - 1) << 7) | DCINWKEN));
    CLEAR_BIT(PWRCON4, DCINWKF);     /* clear pending */
#else
    CLEAR_BIT(CHGCON4, DCINWKEN);
#endif

#if 0
    // CONFIG_POWER_USING_VKEY_WK
    PWRCON0 |= (1 << 4);    /* enable VKEY detect */
    PWRCON2 &= ~(1 << 1);   /* clear pending */
#else
    CLEAR_BIT(PWRCON0, 1 << 4);
#endif

#if (CONFIG_POWER_USING_WDOG_WK > 0)
    SET_BIT(CLKCON0, WDTWKEN_BIT);
#else
    CLEAR_BIT(CLKCON0, WDTWKEN_BIT);
#endif

#if (CONFIG_POWER_USING_P03_WK > 0)
    SET_BIT(P1MOD, P03WKEN);
#else
    CLEAR_BIT(P1MOD, P03WKEN);
#endif

#if (CONFIG_POWER_USING_P04_WK > 0)
    SET_BIT(P1MOD, P04WKEN);
#else
    CLEAR_BIT(P1MOD, P04WKEN);
#endif

#if (CONFIG_POWER_USING_P06_WK > 0)
    SET_BIT(P1MOD, P06WKEN);
#else
    CLEAR_BIT(P1MOD, P06WKEN);
#endif

#if (CONFIG_POWER_USING_P07_WK == 0)
    CLEAR_BIT(PWKCTL, P07WKM_CLR);
#endif

#if (CONFIG_POWER_USING_P11_WK == 0)
    CLEAR_BIT(PWKCTL, P11WKM_CLR);
#endif
}

void power_exit(void)
{
    /* TODO */
}

void power_prepare_stop(void)
{
    /*
     * clear all pendings
     */
#if (CONFIG_POWER_USING_DCIN_WK > 0)
    CLEAR_BIT(PWKCTL, DCINWKF);   /* clear pending */
#endif

#if 0
    // CONFIG_POWER_USING_VKEY_WK
    PWRCON2 &= ~(1 << 1);   /* clear pending */
#endif

#if (CONFIG_POWER_USING_P03_WK > 0)
    _P03_IO_INPUT();
    sys_delay_ms(1);
    if (P0_3 == 1) {
        CLEAR_BIT(P1OM, P03WKEM);        /* low level */
    } else {
        SET_BIT(P1OM, P03WKEM);           /* high level */
    }
    
    P03WKF = 0;             /* clear pending */
#endif

#if (CONFIG_POWER_USING_P04_WK > 0)
    // gpio_input(0x04);
    _P04_IO_INPUT();
    sys_delay_ms(1);
    if (P0_4 == 1) {
        CLEAR_BIT(P1OM, P04WKEM);      /* low level */
    } else {
        SET_BIT(P1OM, P04WKEM);         /* high level */
    }

    CLEAR_REG(P04WKF);             /* clear pending */
#endif

#if (CONFIG_POWER_USING_P06_WK > 0)
    _P06_IO_INPUT(SET);
    sys_delay_ms(1);
    if (P0_6 == 1) {
        CLEAR_BIT(MFP3, P06WKM_CLR);                               /* low level */
    } else {
        MODIFY_REG(MFP3, P06WKM_CLR, P06WKM_HL);            /* high level */
    }

    CLEAR_REG(P06WKF);             /* clear pending */
#endif

#if (CONFIG_POWER_USING_P07_WK > 0 || CONFIG_POWER_USING_P20_WK > 0)
    #if (CONFIG_POWER_USING_P07_WK > 0)
        SET_BIT(P0MOD, 0x80);
        sys_delay_ms(1);
        #if (CONFIG_POWER_USING_P07_WK <= 6)
            MODIFY_REG(PWKCTL, P07WKM_CLR, CONFIG_POWER_USING_P07_WK << 0);
        #elif (CONFIG_POWER_USING_P07_WK == 7)
            if (_P07) {
                MODIFY_REG(PWKCTL, P07WKM_CLR, P07WKM_LL);                 /* low level */
            } else {
                MODIFY_REG(PWKCTL, P07WKM_CLR, P07WKM_HL);          /* high level */
            }
        #elif (CONFIG_POWER_USING_P20_WK == 7)
            if (_P20) {
                MODIFY_REG(PWKCTL, P07WKM_CLR, P07WKM_LL);                 /* low level */
            } else {
                MODIFY_REG(PWKCTL, P07WKM_CLR, P07WKM_HL);          /* high level */
            }
        #else
        #error "P07 IS NOT SUPPORT THIS WAKEUP WAY"
        #endif
    #endif
    #if (CONFIG_POWER_USING_P20_WK > 0)
        SET_BIT(PCON, P20WKEN);
    #endif
    CLEAR_BIT(PWKCTL, P07WKF);    /* clear pending */
#endif

#if (CONFIG_POWER_USING_P11_WK > 0)
    _P11_IO_INPUT(SET);
    sys_delay_ms(1);
    if (P1_1 == 1) {
        MODIFY_REG(PWKCTL, P11WKM_CLR, P11WKM_LL);       /* low level */
    } else {
        MODIFY_REG(PWKCTL, P11WKM_CLR, P11WKM_HL);       /* high level */
    }
    CLEAR_BIT(PWKCTL, P11WKF);    /* clear pending */
#endif
}

/*
 * Put system to STOP mode, after wakeup,
 * the wakeup reason(a bitmask) will return.
 */
uint8_t power_enter_stop(void)
{
    CLEAR_BIT(CLKCON0, SCLKSRC_BIT);        /* switch SCLK to LORC */
    CLEAR_BIT(RCCON, HIRCEN);          /* disable HIRC */
    IRQ_DISABLE();          /* disable EA */
    CPU_STOP();             /* enter to STOP mode */
    
    /******wakeup******/

    SET_BIT(RCCON, HIRCEN);           /* enable HIRC */
    while (!READ_BIT(RCCON, HIRCSTB));/* wait HIRC stable */
    SET_BIT(CLKCON0, SCLKSRC_BIT);        /* switch SCLK to HIRC */

    return get_wakeup_reason();
}

static uint8_t get_wakeup_reason(void)
{
    uint8_t wakeup_reason = 0;

    /* wakeup from STOP mode */
#if (CONFIG_POWER_USING_DCIN_WK > 0)
    if (PWRCON4 & (1 << 5))
        wakeup_reason |= POWER_DCINWKF;
#endif
#if 0
    // CONFIG_POWER_USING_VKEY_WK
    if (PWRCON2 & (1 << 1))
        wakeup_reason |= POWER_VKEYWKF;
#endif
#if (CONFIG_POWER_USING_P03_WK > 0)
    if (P03WKF)
        wakeup_reason |= POWER_P03WKF;
#endif
#if (CONFIG_POWER_USING_P04_WK > 0)
    if (P04WKF)
        wakeup_reason |= POWER_P04WKF;
#endif
#if (CONFIG_POWER_USING_P06_WK > 0)
    if (P06WKF)
        wakeup_reason |= POWER_P06WKF;
#endif
#if (CONFIG_POWER_USING_P07_WK > 0)
    if (READ_BIT(PWKCTL, P07WKF))
        wakeup_reason |= POWER_P07WKF;
#endif
#if (CONFIG_POWER_USING_P11_WK > 0)
    if (READ_BIT(PWKCTL, P11WKF))
        wakeup_reason |= POWER_P11WKF;
#endif
#if (CONFIG_POWER_USING_WDOG_WK > 0)
    if (wakeup_reason == 0)
        wakeup_reason |= POWER_WDOGWKF;
#endif

    return wakeup_reason;
}